16. LiveData Testing Basics

L5 P2 A05 LiveData Testing Basics V2

In this step, you'll finish your addNewTask_setsNewTaskEvent() test.

Step 1: Add InstantTaskExecutor

  1. Add the gradle dependency.

app/build.gradle

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
  1. Open TasksViewModelTest.kt.
  2. Add the InstantTaskExecutorRule inside the TasksViewModelTest class:

TasksViewModelTest.kt

class TasksViewModelTest {
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    // Other code…
}

Step 2: Add the LiveDataTestUtil.kt Class

You'll not create the LiveDataTestUtil class so you can use the getOrAwaitValue Kotlin extension function.

  1. Make a new Kotlin file called LiveDataTestUtil.kt in your test source set:

  1. Copy and paste this code there:

LiveDataTestUtil.kt

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException


@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)

    try {
        afterObserve.invoke()

        // Don't wait indefinitely if the LiveData is not set.
        if (!latch.await(time, timeUnit)) {
            throw TimeoutException("LiveData value was never set.")
        }

    } finally {
        this.removeObserver(observer)
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}

For a full explanation of what this class, check out this blog post.

As you write your own tests that test LiveData, you can similarly copy and use this class in your code.

Step 2: Use getOrAwaitValue to Write the Assertion

Let's use the getOrAwaitValue method and write an assert statement that checks that the newTaskEvent was triggered:

  1. Open TaskViewModelTest.kt.
  2. Get the LiveData value for newTaskEvent using getOrAwaitValue:

TasksViewModelTest.kt

val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
  1. Assert that the value is not null:

TasksViewModelTest.kt

assertThat(value.getContentIfNotHandled(), (not(nullValue())))

The complete test should look like this:

TasksViewModelTest.kt

@Test
fun addNewTask_setsNewTaskEvent() {

    // Given a fresh ViewModel
    val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

    // When adding a new task
    tasksViewModel.addNewTask()

    // Then the new task event is triggered
    val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
    assertThat(
        value.getContentIfNotHandled(), (not(nullValue()))
    )
}
  1. Run your code and watch the test pass!